package tasks

import java.sql.Timestamp
import java.time.{Instant, LocalDateTime, ZoneId}
import akka.actor.{ActorRef, ActorSystem, Props}

import javax.inject.Inject
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import models.tasks._
import play.api.{Configuration, Logging}
import services.OutStockPostParameters
import utiliy.ServerSetting

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, ExecutionContext, Future}

class DynamicJobSchedule @Inject()(actorSystem: ActorSystem,
                                   jobsRepo: JobsRepository,
                                   jobExecRepo: JobExecutionsRepository,
                                   jobSchedules: JobSchedules,
                                   config: Configuration,
                                  )(implicit executionContext: ExecutionContext)
  extends Logging {

  val myActorRef = actorSystem.actorOf(Props(new DynamicJobScheduleActor))
  val myCaseClassMessage = "test"
  val scheduler = QuartzSchedulerExtension(actorSystem)

  val conf = config.get[ServerSetting]("")

  def saveJob(id: String = "", name: String, cronExpression: Option[String], description: Option[String]) = {
    val hash = security.Encode
    val jobId = if (id.isEmpty || true) name +"_"+ hash.hashFNV1a32(name + cronExpression.getOrElse("")).toHexString else id
    val job = JobInfo(id = jobId, name = name, cronExpression = cronExpression, description = description)
    loadJobs(id)
    val res = jobsRepo.create(job)
    println(res)
    res
  }

  def findAllJob = {
    jobsRepo.findAll
  }

  def findJob(id: String): Future[JobInfo] = {
    val jobInfo = jobsRepo.findBy(id)
    jobInfo.map(f =>
      if (f.nonEmpty) f.head
      else JobInfo(name = "")
    )
  }

  def loadJobs(id: String = "", status: Boolean = false) = {
    //myActorRef ! "test"
    if (conf.product) {
      val f = if (id.isEmpty) jobsRepo.findAll
      else {
        Await.result(jobsRepo.upStatus(id, status), Duration.Inf)
        jobsRepo.findBy(id)
      }
      f.map { job =>
        job.foreach(f => {
          if (f.status) runJob(f)
          else stopJob(f.id)
        })
      }
    }else{
      println(
        "***********************************\n" +
        "**                               **\n" +
        "**   this isn't product server   **\n" +
        "**                               **\n" +
        "***********************************\n"
      )
    }
  }

  private def runJob(jobInfo: JobInfo) = {
    /**
     * create Job or update Job
     */
    val res = createJob(scheduleJobName = jobInfo.id, messageSentToReceiver = DynamicJobScheduleMessage(id = jobInfo.id, jobInfo, jobSchedules), scheduleCronExpression = jobInfo.cronExpression.getOrElse("*/1 * * ? * *"))
    logger.info(s"runJob res = ${res}")
    if (res.isInstanceOf[Int]){
      if (res.asInstanceOf[Int] < 0) jobsRepo.upStatus(jobInfo.id, false) else jobsRepo.upStatus(jobInfo.id, true)
      false
    }else{
      val nextRunTime = LocalDateTime.ofInstant( res.asInstanceOf[java.util.Date].toInstant,ZoneId.systemDefault())
      jobsRepo.upNextRunTime(jobInfo.id,Timestamp.valueOf(nextRunTime))
      DynamicJobSchedule.jobSchedules(jobInfo.id) = jobInfo
      logger.info(s"Job [${jobInfo.id}] started ($res)")
      true
    }
  }

  private def stopJob(scheduleJobName: String = "myJobName_1") = {
    try {
      if (scheduler.deleteJobSchedule(name = scheduleJobName)) {
        // Do something if deletion succeeded
        logger.info(s"Job [$scheduleJobName] stoped")
        DynamicJobSchedule.jobSchedules.remove(scheduleJobName)
        1
      } else {
        // Do something else if deletion failed
        logger.info(s"Job [$scheduleJobName] stoped fail")
        0
      }
    } catch {
      case e: Exception =>
        // Take action in case an exception is thrown
        logger.debug("stopJob Exception: ", e)
        -1
    }


  }

  private def createJob(scheduleJobName: String = "myJobName_1",
                        messageReceiverActor: ActorRef = myActorRef,
                        messageSentToReceiver: AnyRef = myCaseClassMessage,
                        scheduleCronExpression: String = "*/10 * * ? * *",
                        description: String = "test",
                        aCalendarName: Option[String] = Some("HourOfTheWolf"),
                        aTimeZone: java.util.TimeZone = java.util.TimeZone.getTimeZone("Asia/Shanghai")
                       ) = {
    try {
      scheduler.createJobSchedule(
        name = scheduleJobName,
        receiver = messageReceiverActor,
        msg = messageSentToReceiver,
        cronExpression = scheduleCronExpression,
        description = Some(description),
//        calendar = aCalendarName,
        timezone = aTimeZone)
    } catch {
      case iae: IllegalArgumentException => {
        logger.debug("Jobs create fail: ", iae)
        iae.toString.indexOf("A schedule with this name already exists")
      } // Do something useful with it.
    }
  }

  private def updateJob(scheduleJobName: String = "myJobName_1",
                        messageReceiverActor: ActorRef = myActorRef,
                        messageSentToReceiver: AnyRef = myCaseClassMessage,
                        scheduleCronExpression: String = "*/10 * * ? * *",
                        description: String = "",
                        aCalendarName: Option[String] = Some("HourOfTheWolf"),
                        aTimeZone: java.util.TimeZone = java.util.TimeZone.getTimeZone("Asia/Shanghai")
                       ) = {
    try {
      scheduler.updateJobSchedule(
        name = scheduleJobName,
        receiver = messageReceiverActor,
        msg = messageSentToReceiver,
        cronExpression = scheduleCronExpression,
        description = Some(description),
//        calendar = aCalendarName,
        timezone = aTimeZone
      )
    } catch {
      case iae: IllegalArgumentException => logger.debug("Jobs update fail: ", iae) // Do something useful with it.
    }
  }

  def deleteJob(scheduleJobName: String = "myJobName_1") = {
    val status = stopJob(scheduleJobName)
    println(scheduleJobName)
    jobsRepo.findBy(scheduleJobName).map { f =>
      println(status)
      if (f.nonEmpty) {
        if (status != 0 || (!f.head.status)) {
          jobsRepo.delete(scheduleJobName)
          1
        } else {
          println("not find")
          0
        }
      } else {
        println("not job")
        0
      }
    }
  }

  def jobExecutionsName = {
    jobExecRepo.findAllName
  }

  def jobExecutionsList(p: OutStockPostParameters) = {
    jobExecRepo.findAll(p)
  }

  def jobExecutionsRemove(id: Option[Int] = None, sdate: String = "", name: String = "") = {
    if (id.nonEmpty) {
      jobExecRepo.delete(id)
    } else {
      jobExecRepo.delete()
    }
  }

  def getAllJobName = {
    DynamicJobSchedule.getAllJobName(jobSchedules)
  }

}

object DynamicJobSchedule {
  var count = 0
  val jobSchedules = mutable.Map[String, JobInfo]()

  def getAllJobName(jobSchedules: JobSchedules) = {
    val arr = new ArrayBuffer[String]()
    for (j <- jobSchedules.values){
      arr += j.toString
    }
    arr.toArray
  }
}

case class LayuiJobExecution(id: Int, status: String, started: String, finished: String, runtime: String, exception: String, traceback: String, job_id: String, name: String)

case class DynamicJobScheduleMessage(id: String, jobInfo: JobInfo, jobSchedules: JobSchedules)
